package sketch.ast;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Modifier;

import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BlockComment;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeParameter;
//import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;

import plume.Pair;

import randoop.util.Files;
import sketch.main.SketchTestOptions;
import sketch.ounit.OUnit;
import sketch.ounit.Observer;
import sketch.specs.MethodDumperAfterRemovingUnusedVars;
import sketch.specs.RedundantParenthesesRemover;
import sketch.specs.SketchedMethodDeclaration;
import sketch.specs.SpecValueDeclarationRemover;
import sketch.specs.VariableDeclarationStatementsVisitor;
import sketch.specs.annotation.TestSketch;
import sketch.util.Checker;
import sketch.util.Globals;
import sketch.util.SketchUtil;

public class ASTUtils {
	
	private static Map<String, CompilationUnit> cachedAST = new LinkedHashMap<String, CompilationUnit>();
	
	public static MethodDeclarationWithHoles createMethodWithHoles(String filePath) {
		CompilationUnit unit = ASTUtils.astRoot(filePath);
		List<AbstractTypeDeclaration> types = ASTUtils.typeDeclarations(unit);
		TypeDeclaration typeDeclaration = ASTUtils.getTypeDeclaration(types, "GenTest");
		MethodDeclaration methodDeclaration = ASTUtils.getMethodByName(typeDeclaration, "scratchTest");
		//check that scratchTest must exist
		Checker.checkNull(methodDeclaration, "There is no method called scratchTest");
		
		HoleExtractionVisitor visitor = new HoleExtractionVisitor(methodDeclaration);
		visitor.visit(methodDeclaration);
		
		MethodDeclarationWithHoles methodHoles = visitor.getTransformedMethod();
		methodHoles.removeAnnotation();
//		methodHoles.rename("testGen1");
//		System.out.println(methodHoles);
		
		return methodHoles;
	}
	
	public static File outputUnit(CompilationUnit unit, String filePath) {
		return outputUnit(unit, new File(filePath));
	}
	
	public static File outputUnit(CompilationUnit unit, File file) {
		Checker.checkNull(unit, "The unit can not be null.");
		Checker.checkNull(file, "The file path could not be null.");
		//output it
		try {
			File parentFile = new File(file.getParent());
			if(!parentFile.exists()) {
				parentFile.mkdirs();
			}
			Files.writeToFile(ASTFormatter.formatUnit(unit.toString()), file);
			return file;
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Can not output unit to file: " + file);
		}
	}
	
	public static void setTypeName(AbstractTypeDeclaration type, String typeName) {
		type.getName().setIdentifier(typeName);
	}
	
	public static String convertPackageDeclToDir(PackageDeclaration package_decl) {
		if(package_decl == null) {
			return null;
		} else {
			String packageName = package_decl.getName().toString();
			return SketchUtil.transClassNameDotToSlash(packageName);
		}
	}
	
	public static void setPackageDeclaration(CompilationUnit unit, String packageName) {
		//if(unit.getPackage() == null) {
			AST ast = unit.getAST();
			PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
			Name package_name = packageName.indexOf(".") == -1 ? ast.newSimpleName(packageName) :
				createQualifiedName(ast, packageName);
			packageDeclaration.setName(package_name);
		//} else {
			unit.setPackage(packageDeclaration);
		//}
	}
	
	//a qualified name => Name.SimpleName
	public static QualifiedName createQualifiedName(AST ast, String string) {
		Checker.checkTrue(string.indexOf(".") != -1, "Illega string: " + string  + " for qualified name.");
		String[] qualifiedNames = string.split("\\.");
		Checker.checkTrue(qualifiedNames.length > 0, "The size: " + qualifiedNames.length
				+ " for string: " + string);
		//QualifiedName ret = null;
		SimpleName name = ast.newSimpleName(qualifiedNames[0]);
		//QualifiedName ret = null;
		Name newQualifiedName = name; 
		for(int i = 1; i < qualifiedNames.length; i++) {
			newQualifiedName = ast.newQualifiedName(newQualifiedName, ast.newSimpleName(qualifiedNames[i]));
		}
		return (QualifiedName)newQualifiedName;
	}
	
	public static<T extends ASTNode> T getParent(ASTNode ast, Class<T> clazz) {
		//System.out.println(ast.getParent());
		Checker.checkNull(ast, "The ast could not be null");
		ASTNode parent = ast;
		while(!(randoop.util.Reflection.canBeUsedAs(parent.getClass(), clazz))) {
			//Checker.checkNull(parent.getParent(), "not null: " + parent);
			parent = parent.getParent();
			if(parent == null) {
				break;
			}
		}
		return (T)parent;
	}
	
	public static CompilationUnit astRoot(File f) {
		return astRoot(f.getAbsolutePath());
	}
	
	public static CompilationUnit astRoot(String filePath) {
		File file = new File(filePath);
		Checker.checkTrue(file.exists(), "The file to parse: " + filePath + " does not exist!");
//		if(cachedAST.containsKey(filePath)) {
//			return cachedAST.get(filePath);
//		}
		
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		//try to set the source and environment
		//read file contents
		String fileContent = null;
		try {
			fileContent = Files.getFileContents(file);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		Checker.checkNull(fileContent, "The file content should not be null!");
		parser.setSource(fileContent.toCharArray());
		//sets the environment when no eclipse java project is available
		//parser.setEnvironment(classpathEntries, sourcepathEntries, encodings, includeRunningVMBootclasspath)
		//set parser options
		parser.setResolveBindings(false); //change to false
		parser.setStatementsRecovery(false);
		//return node
		ASTNode root = parser.createAST(null);
		
		Checker.checkTrue(root instanceof CompilationUnit, "The type of node: " + root.getClass()
				+ " is not CompilationUnit.");
		
		CompilationUnit unit = (CompilationUnit)root;
		cachedAST.put(filePath, unit);
		return unit;
	}
	
	public static void resetSimpleNameIdentifier(SimpleName name,
			Expression expr) {
		//this is added after adding a variable declaration
		//see SketchedBlock
		if(expr instanceof SimpleName) {
			SimpleName expr_name = (SimpleName)expr;
			name.setIdentifier(expr_name.getIdentifier());
			return;
		}
		ASTNode parent = name.getParent();
		if(parent instanceof PostfixExpression) {
			PostfixExpression postExpr = (PostfixExpression)parent;
			System.err.println(postExpr.getOperand());
			Expression insertExpr = ASTUtils.deepClone(expr);
			insertExpr.delete();
			postExpr.setOperand(insertExpr);
//			1++;
			return;
		}
		if(!(parent instanceof MethodInvocation) && !(parent instanceof ClassInstanceCreation)
				&& !(parent instanceof EnhancedForStatement) && !(parent instanceof org.eclipse.jdt.core.dom.ArrayAccess)
				&& (!(parent instanceof QualifiedName)) && (!(parent instanceof InfixExpression))) {
			throw new RuntimeException("wrong parent type: " + parent.getClass() + ", for name: " + name
					+ ", its parent is: " + parent + ", the expression type is: " + parent.getClass());
		}
		
		if (parent instanceof ArrayAccess) {
			ArrayAccess ac = (ArrayAccess)parent;
//			Checker.checkTrue(ac.getArray() instanceof SimpleName, "The array expression" +
//					" should be simple name, but now it is: " + ac.getArray());
//			((SimpleName)ac.getArray()).setIdentifier(identifier)
			Expression insertExpr = ASTUtils.deepClone(expr);
			insertExpr.delete();
			ac.setArray(insertExpr);
		} else if(parent instanceof QualifiedName) {
			//XXX hard code here for work around experiment near deadlien
			//shoud fix
			QualifiedName qname = (QualifiedName)parent;
			((SimpleName)(qname.getQualifier())).setIdentifier(expr.toString());
			
		} else if (parent instanceof InfixExpression) {
			InfixExpression infixExpr = (InfixExpression)parent;
			Expression leftName = infixExpr.getLeftOperand();
			if(leftName == name) {
				Expression insertExpr = ASTUtils.deepClone(expr);
				insertExpr.delete();
				infixExpr.setLeftOperand(insertExpr);
			}
			if(infixExpr.getRightOperand() == name) {
				Expression insertExpr = ASTUtils.deepClone(expr);
				insertExpr.delete();
				infixExpr.setRightOperand(insertExpr);
			}
		} else {
			List<Expression> arguments = null;
			SimpleName receiver = null;
			if (parent instanceof MethodInvocation) {
				MethodInvocation parentInvoke = (MethodInvocation) parent;
				arguments = parentInvoke.arguments();
				Expression receiver_expr = parentInvoke.getExpression();
				if (receiver_expr instanceof SimpleName) {
					receiver = (SimpleName) receiver_expr;
				}
			} else if (parent instanceof ClassInstanceCreation) {
				ClassInstanceCreation constructorInvoke = (ClassInstanceCreation) parent;
				arguments = constructorInvoke.arguments();
			} else if (parent instanceof EnhancedForStatement) {
				EnhancedForStatement forStatement = (EnhancedForStatement) parent;
				// useless, just for placeholder
				arguments = new LinkedList<Expression>();
				arguments.add(forStatement.getExpression());

				// the real replacement here
				Expression insertExpr = ASTUtils.deepClone(expr);
				insertExpr.delete();
				forStatement.setExpression(insertExpr);
			}

			Checker.checkNull(arguments, "The argument list could not be null.");

			int index = arguments.indexOf(name);
			Checker.checkTrue(index != -1 || receiver != null,
					"The value of index: " + index
							+ " is illegal!, or the receiver can not be null");
			if (index != -1) {
				arguments.remove(index);
				Expression insertExpr = ASTUtils.deepClone(expr);
				insertExpr.delete();
				arguments.add(index, insertExpr);
			} else if (receiver != null) {
				// XXX only do name replacement
				receiver.setIdentifier(expr.toString());
			}
		}
	}
	
	public static Collection<SimpleName> getDeclaredVariableNames(VariableDeclarationStatement statement) {
		List<SimpleName> names = new LinkedList<SimpleName>();
		List<VariableDeclarationFragment> fragments = statement.fragments();
		for(VariableDeclarationFragment fragment : fragments) {
			names.add(fragment.getName());
		}
		return names;
	}
	
	public static List<VariableDeclarationStatement> getVariableDeclarations(Block block) {
		List<VariableDeclarationStatement> list = new LinkedList<VariableDeclarationStatement>();
		List<Statement> statements = block.statements();
		for(Statement statement : statements) {
			if(statement instanceof VariableDeclarationStatement) {
				list.add((VariableDeclarationStatement)statement);
			}
		}
		return list;
	}
	
	public static Collection<VariableDeclarationStatement> getVariableDeclarationsRecursively(ASTNode node) {
		VariableDeclarationStatementsVisitor visitor = new VariableDeclarationStatementsVisitor();
		node.accept(visitor);
		return visitor.getDeclaredStatements();
	}
	

	public static List<MethodDeclaration> getAllMethods(CompilationUnit unit) {
		List<MethodDeclaration> methods = new LinkedList<MethodDeclaration>();
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		
		for(AbstractTypeDeclaration type : types) {
			if(type instanceof TypeDeclaration) {
				for(MethodDeclaration method : ((TypeDeclaration) type).getMethods()) {
					methods.add(method);
				}
			}
		}
		
		return methods;
	}
	
	public static void removeAllSketchTestNotationsAndImports(CompilationUnit unit) {
		//first remove all imports
		List<ImportDeclaration> imports = unit.imports();
		List<ImportDeclaration> needToRemove = new LinkedList<ImportDeclaration>();
		for(ImportDeclaration ip : imports) {
			if(ip.toString().startsWith("import sketch.")) {
				needToRemove.add(ip);
			}
		}
		imports.removeAll(needToRemove);
		
		//then remove all method notations
		for(MethodDeclaration m : ASTUtils.getAllMethods(unit)) {
			removeMethodAnnotation(m, TestSketch.class, OUnit.class);
		}
	}
	
	public static void removeMethodAnnotation(MethodDeclaration method, Class<?>...classes) {
		List<IExtendedModifier> modifiers = method.modifiers();
		List<IExtendedModifier> needToRemove = new LinkedList<IExtendedModifier>();
		for(IExtendedModifier modifier : modifiers) {
			String str = modifier.toString();
			//System.out.println("annotation: " + str);
			boolean flag = false;
			for(Class<?> clz : classes) {
				if(str.equals("@sketch.ounit.OUnit") || str.equals("@OUnit")
						|| str.equals("@sketch.specs.annotation.TestSketch")
						|| str.equals("@TestSketch")) {
					flag = true;
					break;
				}
			}
			if(flag) {
				needToRemove.add(modifier);
			}
		}
		//remove it
		modifiers.removeAll(needToRemove);
	}
	
	public static void removeSpecValue(MethodDeclaration method) {
		 SpecValueDeclarationRemover spec_value_remover = new SpecValueDeclarationRemover();
		spec_value_remover.visit(method);
		//remove it
		spec_value_remover.remove_var_decls();
	}
	
	public static void removeMethodAnnotation(MethodDeclaration method, Class<?> cz) {
		List<IExtendedModifier> modifiers = method.modifiers();
		List<IExtendedModifier> needToRemove = new LinkedList<IExtendedModifier>();
		for(IExtendedModifier modifier : modifiers) {
			if((modifier instanceof NormalAnnotation)) {
				NormalAnnotation annot = (NormalAnnotation)modifier;
				String str = annot.getTypeName().toString();
				//System.out.println("--- " + str + "   " + cz.getName() + ",,  type: " + modifier.getClass());
				if(str.equals(cz.getName()) || str.equals(cz.getSimpleName())) {
					needToRemove.add(modifier);
				}
			} else if (modifier instanceof MarkerAnnotation) {
				MarkerAnnotation annot = (MarkerAnnotation)modifier;
				String str = annot.getTypeName().toString();
				//System.out.println("--- " + str + "   " + cz.getName() + ",,  type: " + modifier.getClass());
				if(str.equals(cz.getName()) || str.equals(cz.getSimpleName())) {
					needToRemove.add(modifier);
				}
			}
		}
		//remove it
		modifiers.removeAll(needToRemove);
	}
	
	public static NormalAnnotation getMethodAnnotation(MethodDeclaration method, Class<?> clz) {
		List<IExtendedModifier> modifiers = method.modifiers();
		for(IExtendedModifier modifier : modifiers) {
			if(!(modifier instanceof NormalAnnotation)) {
				continue;
			}
			NormalAnnotation annot = (NormalAnnotation)modifier;
			String str = annot.getTypeName().toString();
			if(str.equals(clz.getName()) || str.equals(clz.getSimpleName())) {
				return annot;
			}
		}
		return null;
	}
	
	public static void removeTestSketches(CompilationUnit unit) {
		removeMethodsWithAnnotation(unit, TestSketch.class.getSimpleName());
	}
	
	public static void removeMethodsWithAnnotation(CompilationUnit unit, String annotation) {
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		for(AbstractTypeDeclaration type : types) {
			if(type instanceof TypeDeclaration) {
				List<MethodDeclaration> needToRemove = getMethodsWithAnnotation((TypeDeclaration)type, annotation);
				type.bodyDeclarations().removeAll(needToRemove);
			}
		}
	}
	
	public static List<MethodDeclaration> getMethodsWithAnnotation(CompilationUnit unit, String annotation) {
		List<MethodDeclaration> methods = new LinkedList<MethodDeclaration>();
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		
		for(AbstractTypeDeclaration type : types) {
			if(type instanceof TypeDeclaration) {
				methods.addAll(getMethodsWithAnnotation((TypeDeclaration)type, annotation));
			}
		}
		
		return methods;
	}
	
	public static TypeDeclaration findOutType(CompilationUnit unit, MethodDeclaration method) {
		TypeDeclaration type = null;
		
		List<AbstractTypeDeclaration> abTypes = unit.types();
		for(AbstractTypeDeclaration abType : abTypes) {
			if(abType instanceof TypeDeclaration) {
				TypeDeclaration typeDecl = (TypeDeclaration)abType;
				boolean found = false;
				for(MethodDeclaration m : typeDecl.getMethods()) {
					if(m.equals(method)) {
						found = true;
						break;
					}
				}
				if(found) {
					type = typeDecl;
					break;
				}
			}
		}
		
		return type;
	}
	
	
	public static List<MethodDeclaration> getMethodsWithAnnotation(TypeDeclaration  type, String annotationName) {
		List<MethodDeclaration> methodDecls = new LinkedList<MethodDeclaration>();
		MethodDeclaration[] methods = type.getMethods();
		for(MethodDeclaration method : methods) {
			List<IExtendedModifier> modifiers = method.modifiers();
			for(IExtendedModifier modifier : modifiers) {
				if(modifier.isAnnotation()) {
					//System.out.println(modifier + ", type: " + modifier.getClass());
					if(modifier.toString().equals("@" + annotationName)) {
						methodDecls.add(method);
						break;
					}
				}
			}
		}
		
		return methodDecls;
	}
	
	public static boolean isPublic(MethodDeclaration declaration) {
		List<IExtendedModifier> modifiers = declaration.modifiers();
		boolean ispublic = false;
		
		for(IExtendedModifier modifier : modifiers) {
			if(modifier.toString().equals("public")) {
				ispublic = true;
				break;
			}
		}
		
		return ispublic;
	}
	
	public static void addOUnitAnnotation(MethodDeclaration method) {
		if(hasOUnitAnnotation(method)) {
			return;
		}
		List<IExtendedModifier> modifiers = method.modifiers();
		//XXX should check here
		//create an annotation
		MarkerAnnotation annotation = method.getAST().newMarkerAnnotation();
		annotation.setTypeName(method.getAST().newName(OUnit.class.getName()));
		modifiers.add(0, annotation);
	}
	
	public static boolean hasOUnitAnnotation(MethodDeclaration method) {
		//System.err.println(OUnit.class.getName());
		List<IExtendedModifier> modifiers = method.modifiers();
		for(IExtendedModifier modifier : modifiers) {
			if(modifier.toString().equals("@" + OUnit.class.getName())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Makes a given method declaration to be public, if that is already public,
	 * just do nothing.
	 */
	public static void makeMethodPublic(MethodDeclaration declaration) {
		List<IExtendedModifier> modifiers = declaration.modifiers();
		int indexOfRemovingModifier = -1;
		for(int i = 0; i < modifiers.size(); i++) {
			IExtendedModifier modifier = modifiers.get(i);
			if(modifier instanceof org.eclipse.jdt.core.dom.Modifier) {
				String str = modifier.toString();
				if(str.equals("public")) {
					//it is already public, return it without doing anything
					return;
				} else if (str.equals("private") || str.equals("protected")) {
					indexOfRemovingModifier = i;
					break;
				}
			}
		}
		if(indexOfRemovingModifier != -1) {
			modifiers.remove(indexOfRemovingModifier);
		}
		//insert the public modifier here
		IExtendedModifier publicModifier = 
			declaration.getAST().newModifier(org.eclipse.jdt.core.dom.Modifier.ModifierKeyword.PUBLIC_KEYWORD);
		if(indexOfRemovingModifier != -1) {
			modifiers.add(indexOfRemovingModifier, publicModifier);
		} else {
			modifiers.add(0, publicModifier);
		}
	}
	
	public static void renameAllTestsInCompilationUnit(CompilationUnit unit) {
		List<MethodDeclaration> methods = ASTUtils.getAllMethods(unit);
		Map<String, Integer> method_index_map = new LinkedHashMap<String, Integer>();
		for(MethodDeclaration method : methods) {
			String methodName = method.getName().toString();
			if(!methodName.startsWith("test")) {
				continue;
			}
			int rep_index = methodName.indexOf(SketchedMethodDeclaration.replic_sep);
			Checker.checkTrue(rep_index != -1, "The replication index != -1, for: " + methodName);
			String realName = methodName.substring(0, rep_index);
			if(!method_index_map.containsKey(realName)) {
				method_index_map.put(realName, 0);
			}
			int currentIndex = method_index_map.get(realName);
			String newName = realName + currentIndex;
			method_index_map.put(realName, currentIndex + 1);
			method.getName().setIdentifier(newName);
		}
		//reclaim memory
		method_index_map.clear();
	}
	
	/**
	 * Sample usage:
	 * 	List<ImportDeclaration> importList = importDeclarations(unit);
     *  for(ImportDeclaration importDecl : importList) {
     *  System.out.println(importDecl);
     * }
	 * */
	public static List<ImportDeclaration> importDeclarations(CompilationUnit unit) {
		@SuppressWarnings("unchecked")
		List<ImportDeclaration> imports = unit.imports();
		return imports;
	}
	
	@SuppressWarnings("unchecked")
	static List<AbstractTypeDeclaration> typeDeclarations(CompilationUnit unit) {
		return unit.types();
	}
	
	static TypeDeclaration getTypeDeclaration(List<AbstractTypeDeclaration> types, String parentClassName) {
		for(AbstractTypeDeclaration type : types) {
			if(type instanceof TypeDeclaration) {
				TypeDeclaration typeDeclaration = (TypeDeclaration)type;
			    if(typeDeclaration.getSuperclassType().toString().equals(parentClassName)) {
			    	return typeDeclaration;
			    }
			}
		}
		return null;
	}
	
	static MethodDeclaration getMethodByName(TypeDeclaration type, String scratchTestName) {
		MethodDeclaration[] methods = type.getMethods();
		for(MethodDeclaration method: methods) {
			//System.out.println(method.getName());
			if(method.getName().toString().equals(scratchTestName)) {
				return method;
			}
		}
		return null;
    }
	
	//if no method is found, this function will return null instead
	public static MethodDeclaration getMethodBySignature(Collection<MethodDeclaration> methods, MethodDeclaration method) {
		Checker.checkNull(methods, "The methods could not be null.");
		Checker.checkNull(method, "The method for matching could not be null.");
		for(MethodDeclaration m : methods) {
			if(m.getName().toString().equals(method.getName().toString()) && m.getModifiers() == method.getModifiers()) {
				List<TypeParameter> m_types = m.typeParameters();
				List<TypeParameter> method_types = method.typeParameters();
				if(m_types.size() == method_types.size()) {
					boolean matched = true;
					for(int i = 0; i < m_types.size(); i++) {
						if(!m_types.get(i).toString().equals(method_types.get(i).toString())) {
							matched = false;
							break;
						}
					}
					if(matched) {
						return m;
					}
				}
			}
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	private static List<Statement> getStatements(MethodDeclaration methodDeclaration) {
		return methodDeclaration.getBody().statements();
	}
	
	@SuppressWarnings("unchecked")
	private static Map<Integer, List<Pair<Type, SimpleName>>> getAllDeclaredVariables(List<Statement> statements) {
		Map<Integer, List<Pair<Type, SimpleName>>> varDeclMap = new LinkedHashMap<Integer, List<Pair<Type, SimpleName>>>();
		for(int i = 0; i < statements.size(); i++) {
			Statement statement = statements.get(i);
			List<Pair<Type, SimpleName>> declaredVars = new LinkedList<Pair<Type, SimpleName>>();
			//System.out.println(statement + "  : " + statement.getClass());
			if(statement instanceof VariableDeclarationStatement) {
				VariableDeclarationStatement varDecl = (VariableDeclarationStatement)statement;
				List<VariableDeclarationFragment> fragments = varDecl.fragments();
				for(VariableDeclarationFragment fragment : fragments) {
//					System.out.println(varDecl.getType());
//					System.out.println(fragment.getName());
//					System.out.println();
					declaredVars.add(new Pair<Type, SimpleName>(varDecl.getType(), fragment.getName()));
				}
			}
			if(!declaredVars.isEmpty()) {
				varDeclMap.put(i, declaredVars);
			}
		}
		return varDeclMap;
	}
	
	private static Map<Integer, Statement> extractJavaStatements(List<Statement> statements) {
		Checker.checkNull(statements, "The statements input could not be null!");
		Map<Integer, Statement> map = new LinkedHashMap<Integer, Statement>();
		for(int i = 0; i < statements.size(); i++) {
			Statement statement = statements.get(i);
			if(isJavaText(statement)) {
				map.put(i, statement);
			}
		}
		return map;
	}
	
	private static Map<Integer, Statement> extractGeneratorStatements(List<Statement> statements) {
		Checker.checkNull(statements, "The statements input could not be null!");
		Map<Integer, Statement> map = new LinkedHashMap<Integer, Statement>();
		for(int i = 0; i < statements.size(); i++) {
			Statement statement = statements.get(i);
			if(isGeneratorStatement(statement)) {
				map.put(i, statement);
			}
		}
		return map;
	}
	
	/**
	 * The Java source code that need to be presented in the generated test
	 * XXX this implementation is really preliminary yet
	 * */
	private static boolean isJavaText(Statement statement) {
		String statementText = statement.toString();
		if(statementText.indexOf("ValueGenerator<") == -1 && statementText.indexOf("MethodChoice") == -1
				&& statementText.indexOf("OracleChoice") == -1) {
			return true;
		} else {
		    return false;
		}
	}
	
	/**
	 * The generator statement
	 * XXX this implementation is really preliminary yet!
	 * */
	private static boolean isGeneratorStatement(Statement statement) {
		if(statement.toString().indexOf("MethodChoice") != -1 || statement.toString().indexOf("OracleChoice") != -1) {
			return true;
		} else {
		    return false;
		}
	}
	
	public static boolean isGeneratorInvocation(MethodInvocation methodInvoke) {
		String receiver_string = methodInvoke.getExpression().toString();
		if(receiver_string.equals("OracleChoice")
				|| receiver_string.equals("MethodChoice")) {
			return true;
		}
		return false;
	}
	
	public static Map<SimpleName, VariableDeclarationFragment> extractDefinedVariableMapping(Collection<VariableDeclarationStatement> statements) {
		Map<SimpleName, VariableDeclarationFragment> map = new LinkedHashMap<SimpleName, VariableDeclarationFragment>();
		for(VariableDeclarationStatement statement : statements) {
			map.putAll(extractDefinedVariableMapping(statement));
		}
		return map;
	}
	
	public static Map<SimpleName, Type> extractDefinedVariableTypeMapping(Collection<VariableDeclarationStatement> statements) {
		Map<SimpleName, Type> map = new LinkedHashMap<SimpleName, Type>();
		for(VariableDeclarationStatement statement : statements) {
			Type t = statement.getType();
			List<VariableDeclarationFragment> fragments = statement.fragments();
			for(VariableDeclarationFragment fragment : fragments) {
				map.put(fragment.getName(), t);
			}
		}
		return map;
	}
	
	public static Map<SimpleName, VariableDeclarationFragment> extractDefinedVariableMapping(VariableDeclarationStatement statement) {
		Map<SimpleName, VariableDeclarationFragment> map = new LinkedHashMap<SimpleName, VariableDeclarationFragment>();
		List<VariableDeclarationFragment> fragments = statement.fragments();
		for(VariableDeclarationFragment fragment : fragments) {
			map.put(fragment.getName(), fragment);
		}
		return map;
	}
	
	public static List<SimpleName> extractDefinedVariableName(Collection<VariableDeclarationStatement> statements) {
		List<SimpleName> retNames = new LinkedList<SimpleName>();
		for(VariableDeclarationStatement statement : statements) {
			retNames.addAll(extractDefinedVariableName(statement));
		}
		return retNames;
	}
	
	public static List<SimpleName> extractDefinedVariableName(VariableDeclarationStatement statement) {
		List<SimpleName> retNames = new LinkedList<SimpleName>();
		List<VariableDeclarationFragment> fragments = statement.fragments();
		for(VariableDeclarationFragment fragment : fragments) {
			retNames.add(fragment.getName());
		}
		return retNames;
	}
	
	@SuppressWarnings("unchecked")
	public static Expression extractReceiverExpr(MethodInvocation methodInvoke) {
		Checker.checkTrue(isGeneratorInvocation(methodInvoke), "The method: " + methodInvoke + " should be a " +
				" generator invocation.");
		List<Expression> arguments = methodInvoke.arguments();
		Checker.checkTrue(!arguments.isEmpty(), "The argument list should not be empty!");
		Expression expr = arguments.get(0);
		//System.out.println(expr);
		return expr;
	}
	
	private static Map<Integer, Expression> extractReceiverObjectNames(List<Statement> statements) {
		Map<Integer, Expression> map = new LinkedHashMap<Integer, Expression>();
		
		for(int i = 0; i < statements.size(); i++) {
			Statement statement = statements.get(i);
			if(!isGeneratorStatement(statement)) {
				continue;
			}
			//System.out.println(statement);
			Expression receiverObjectExpr = extractReceiverObjectName(statement);
			Checker.checkNull(receiverObjectExpr, "The receiver object name could not be null in statement: " + statement);
			map.put(i, receiverObjectExpr);
		}
		
		return map;
	}
	
	@SuppressWarnings("unchecked")
	private static Expression extractReceiverObjectName(Statement statement) {
		if(statement instanceof ExpressionStatement) {
			ExpressionStatement exprStatement = (ExpressionStatement)statement;
			Expression expression = exprStatement.getExpression();
			if(expression instanceof MethodInvocation) {
				MethodInvocation methodInvocation = (MethodInvocation)expression;
				List<Expression> arguments = methodInvocation.arguments();
				Checker.checkTrue(!arguments.isEmpty(), "The argument list should not be empty!");
				Expression expr = arguments.get(0);
				if(SketchTestOptions.verbose) {
				    System.out.println(expr);
				}
				return expr;
			}
		}

		return null;
	}
	
	public static Map<Integer, Statement> extractJavaStatements(String filePath,
			String extendClassName, String overridenTest) {
		CompilationUnit unit = ASTUtils.astRoot(filePath);
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		TypeDeclaration typeDeclaration = getTypeDeclaration(types, extendClassName);
		MethodDeclaration methodDeclaration = getMethodByName(typeDeclaration, overridenTest);
		List<Statement> statements = getStatements(methodDeclaration);
		Map<Integer, Statement> javaSources = extractJavaStatements(statements);

		return javaSources;
	}
	
	public static Map<Integer, Expression> extractReceiverObjects(String filePath,
			String extendClassName, String overridenTest) {
		CompilationUnit unit = ASTUtils.astRoot(filePath);
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		TypeDeclaration typeDeclaration = getTypeDeclaration(types, extendClassName);
		MethodDeclaration methodDeclaration = getMethodByName(typeDeclaration, overridenTest);
		List<Statement> statements = getStatements(methodDeclaration);
		Map<Integer, Expression> expression = extractReceiverObjectNames(statements);
		return expression;
	}
	
	public static String[] extractImportDeclarationAsString(CompilationUnit unit) {
		List<ImportDeclaration> importDecls = ASTUtils.importDeclarations(unit);
		String[] importStrings = new String[importDecls.size()];
		for(int i = 0; i < importDecls.size(); i++) {
			importStrings[i] = importDecls.get(i).toString();
		}
		return importStrings;
	}
	
	public static String[] extractImportDeclarationsAsString(String filePath) {
//		List<ImportDeclaration> importDecls = extractImportDeclarations(filePath);
//		String[] importStrings = new String[importDecls.size()];
//		for(int i = 0; i < importDecls.size(); i++) {
//			importStrings[i] = importDecls.get(i).toString();
//		}
//		return importStrings;
		return extractImportDeclarationAsString(ASTUtils.astRoot(filePath));
	}
	
	public static List<ImportDeclaration> extractImportDeclarations(String filePath) {
		CompilationUnit unit = ASTUtils.astRoot(filePath);
		return ASTUtils.importDeclarations(unit);
	}
	
	public static List<Comment> blockComments(List<Comment> list) {
		List<Comment> blockComments = new LinkedList<Comment>();
		for(Comment comment : list) {
			if(comment instanceof BlockComment) {
				blockComments.add(comment);
			}
		}
		return blockComments;
	}
	
	public static String dumpMethodWithUnusedVarRemoved(MethodDeclaration method) {
		Checker.checkNull(method, "The method input could not be null!");
		MethodDumperAfterRemovingUnusedVars dumper = new MethodDumperAfterRemovingUnusedVars(method);
		return dumper.dumpMethodWithUnusedVarRemoved();
	}
	
	public static Expression constantToExpr(Object obj, AST ast) {
		if(obj == null) {
			return ast.newNullLiteral();
		}
		if(obj instanceof String) {
			String objValue = obj.toString();
			if(objValue.equals(Observer.NULL)) {
				return ast.newNullLiteral();
			} else if(SketchUtil.isNumber(objValue)) {
				return ast.newNumberLiteral(objValue);
			} else if (objValue.equals("true")) { //XXX could be problematic here,
				return ast.newBooleanLiteral(true);
			} else if (objValue.equals("false")) { //can not distinguished bewteen boolean / string
				return ast.newBooleanLiteral(false);
			}else {
				StringLiteral stringLiteral = ast.newStringLiteral();
				stringLiteral.setLiteralValue(objValue);
				return stringLiteral;
			}
		}
		if(SketchUtil.isPrimitiveOrBoxingType(obj.getClass())) {
			return ast.newNumberLiteral(obj.toString());
		}
		throw new UnsupportedOperationException("The type is not supported: " + obj.getClass());
	}
	
	public static Block closeBlock(ASTNode node) {
		Checker.checkNull(node, "The ASTNode could not be null!");
		//Checker.checkTrue(!(node instanceof Block), "The node type should not be BLock");
//		if(node instanceof Block) {
//			return (Block)node;
//		}
		Block parentBlock = null;
		while(node.getParent() != null) {
			if(node.getParent() instanceof Block) {
				parentBlock = (Block)node.getParent();
				break;
			}
			node = node.getParent();
		}
		return parentBlock;
	}
	
	public static MethodDeclaration closeMethodDeclaration(ASTNode node) {
		Checker.checkNull(node, "The ASTNode could not be null!");
		MethodDeclaration method = null;
		
		ASTNode parent = node.getParent();
		while(!(parent instanceof MethodDeclaration) && parent != null) {
			parent = parent.getParent();
		}
		
		if(parent != null) {
			method = (MethodDeclaration)parent;
		}
		
		return method;
	}
	
	public static AST getAST(Collection<? extends ASTNode> astnodes) {
		Checker.checkTrue(!astnodes.isEmpty(), "The astnodes could not be empty.");
		AST ast = null;
		for(ASTNode node : astnodes) {
			if(ast == null) {
				ast = node.getAST();
			} else {
				if(ast != node.getAST()) {
					throw new RuntimeException("The ast is not the same!");
				}
			}
		}
		Checker.checkNull(ast, "The ast should not be null.");
		return ast;
	}
	
	public static<T extends ASTNode> T deepClone(T node) {
		return (T)node.copySubtree(node.getAST(), node);
	}
	
	public static<T extends ASTNode> List<T> removeTextualRedanduance(List<T> nodes) {
		List<T> afterRemoving = new LinkedList<T>();
		for(T node : nodes) {
			boolean hasAlreadyIn = false;
			for(T in : afterRemoving) {
				if(in.toString().equals(node.toString())) {
					hasAlreadyIn = true;
					break;
				}
			}
			if(!hasAlreadyIn) {
				afterRemoving.add(node);
			}
		}
		return afterRemoving;
	}
	
	public static boolean isNested(ASTNode subNode, ASTNode node) {
		if(subNode == node) {
			return false;
		}
		Checker.checkTrue(subNode.getAST() == node.getAST(), "The AST tree is different, can not compare!");
		int startPosSubNode = subNode.getStartPosition();
		int endPosSubNode = subNode.getStartPosition() + subNode.getLength();
		
		int startPosNode = node.getStartPosition();
		int endPosNode = node.getStartPosition() + node.getLength();
		
		Checker.checkTrue(startPosSubNode != startPosNode && endPosSubNode != endPosNode, "Two nodes could not overlap!");
		
		return (startPosSubNode > startPosNode) && (endPosSubNode < endPosNode);
	}
	
	public static CompilationUnit parseUnit(String sourceString) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_COMPILATION_UNIT);
		parser.setSource(sourceString.toCharArray());
		parser.setResolveBindings(true);
		CompilationUnit unit = (CompilationUnit)parser.createAST(null);
		return unit;
	}
	
	public static MethodDeclaration[] parseMethods(String sourceString) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
		parser.setSource(sourceString.toCharArray());
		parser.setResolveBindings(true);
		TypeDeclaration type = (TypeDeclaration)parser.createAST(null);
		return type.getMethods();
	}
	
	public static Block parseStatements(String sourceString) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_STATEMENTS);
		parser.setSource(sourceString.toCharArray());
		parser.setResolveBindings(true);
		return (Block)parser.createAST(null);
	}
	
	public static Expression parseExpression(String sourceString) {
		ASTParser parser = ASTParser.newParser(AST.JLS3);
		parser.setKind(ASTParser.K_EXPRESSION);
		parser.setSource(sourceString.toCharArray());
		parser.setResolveBindings(true);
		return (Expression)parser.createAST(null);
	}
	
	public static void main(String[] args) {
		String filePath = "/scratch/eclipseworkspace/SketchSeq/examples/TreeMapSketchExecution.java";
		CompilationUnit unit = ASTUtils.astRoot(filePath);
		
		List<AbstractTypeDeclaration> types = typeDeclarations(unit);
		
		TypeDeclaration typeDeclaration = getTypeDeclaration(types, "GenTest");
		
		MethodDeclaration methodDeclaration = getMethodByName(typeDeclaration, "scratchTest");
		
		//System.out.println(methodDeclaration);
		List<Statement> statements = getStatements(methodDeclaration);
		
		Map<Integer, List<Pair<Type, SimpleName>>> declares = getAllDeclaredVariables(statements);
		
		System.out.println(Globals.lineSep);
		System.out.println("--------- let's print out all declarations---------");
		for(Integer i : declares.keySet()) {
			List<Pair<Type, SimpleName>> declList = declares.get(i);
		    for(Pair<Type, SimpleName> typeName : declList) {
			    System.out.println(i + "  -- " +  typeName.a.toString() + " : " + typeName.b);
		    }
		}
		System.out.println();
		
		Map<Integer, Statement> javaSources = extractJavaStatements(statements);
		Map<Integer, Statement> generatorSources = extractGeneratorStatements(statements);
		
		System.out.println("--------- let's print out java sources---------");
		for(Integer i : javaSources.keySet()) {
			System.out.println("  " + i + "  -  " + javaSources.get(i));
		}
		
		System.out.println();
		
		System.out.println("--------- let's print out generator sources---------");
		for(Integer i : generatorSources.keySet()) {
			System.out.println("  " + i + "  -  " + generatorSources.get(i));
		}
		
		System.out.println();
		System.out.println("----------let's print out generato receiver objects ------");
		extractReceiverObjectNames(statements);
	}
}